/* Runtime data source */

#include <cstddef>
#include <cstdlib>
#include <cstring>

#define EXPORT_API

#include <memchart/exception.hpp>
#include <memchart/rt_class_info.hpp>
#include <memchart/types.hpp>

#include "data_sink.hpp"
#include "object_map.hpp"
#include "test_data_sink.hpp"
#include "gtk_data_sink.hpp"

#include <memchart/rt_data_source.hpp>

namespace memchart
{
	/*
	 * The data sink cannot be just an object with static storage
	 * duration, because this leads to static initialization order fiasco
	 * with Gtkmm
	 */
	static data_sink *sink = NULL;
	static object_map objects;

	static void deinitialize ();

	static gtk_data_sink *create_gtk_sink ()
	{
		/* Get command line options for GTK+ */

		char arg0[] = "";
		vector<char *> args(1, arg0);

		const char *s = std::getenv("MC_GTK_PARAMS");
		vector<char> params(1, '\0');
		if (s)
		{
			bool sq = false, dq = false; /* Single/double quote */
			bool prev_space = true, escape = false;

			for ( ; *s ; s++)
			{
				if (escape)
				{
					params.push_back(*s);
					escape = false;
					continue;
				}

				switch (*s)
				{
					case '\\':
						escape = true;
						break;
					case '\'':
						if (!dq)
							sq = !sq;
						else
							params.push_back(*s);
						break;
					case '"':
						if (!sq)
							dq = !dq;
						else
							params.push_back(*s);
						break;
					case ' ':
						if (!sq && !dq)
						{
							if (!prev_space)
								params.push_back('\0');
						}
						else
							params.push_back(*s);
						break;
					default:
						params.push_back(*s);
				}

				prev_space = *s == ' ';
			}
			params.push_back('\0');

			for (vector<char>::iterator it = params.begin() + 1;
				it != params.end() - 1; ++it)
			{
				if (!*(it - 1))
					args.push_back(&*it);
			}
		}

		args.push_back(NULL);
		int argc = args.size() - 1;
		char **argv = &*args.begin();

		/* Create the actual object */
		return new gtk_data_sink(objects, argc, argv);
	}

	/* Initialize the data sink */
	static void initialize_once ()
	{
		if (!sink)
		{
			if (std::atexit(deinitialize) != 0)
				throw error("std::atexit failed");

			const char *backend = std::getenv("MC_BACKEND");

			if (backend)
			{
				string s(backend);

				if (s == "test")
					sink = new test_data_sink(objects);
				else if (s == "gtk")
					sink = create_gtk_sink();
				else
					throw error("unknown backend specified");
			}
			else
			{
				/* Default choice */
				sink = create_gtk_sink();
			}
		}
	}

	/* Destroy the sink object */
	static void deinitialize ()
	{
		delete sink;
	}


	/* Interface functions */

	void rt_data_source::add_obj (void *addr, const rt_class_info & info)
	{
		objects.insert(make_pair(reinterpret_cast<uintptr_t>(addr), &info));
	}

	void rt_data_source::del_obj (void *addr)
	{
		objects.erase(reinterpret_cast<uintptr_t>(addr));
	}

	void rt_data_source::update (const string & s)
	{
		initialize_once();

		sink->update(s);
	}
}
